package net.qiqbframework.modelling.command;

import net.qiqbframework.common.lock.TopicLock;
import net.qiqbframework.messaging.Message;
import net.qiqbframework.modelling.domain.Aggregate;
import net.qiqbframework.modelling.domain.AnnotatedBizIdentifier;

import java.util.function.Supplier;


public class LockAwareAggregate<AR, A extends Aggregate<AR>> implements Aggregate<AR> {

    private final A wrappedAggregate;
    private final LockSupplier lock;

    /**
     * Initializes a new {@link LockAwareAggregate} for given {@code wrappedAggregate} and {@code lock}.
     *
     * @param wrappedAggregate the aggregate instance to which the LockAwareAggregate will delegate
     * @param lock             the lock held by the aggregate
     */
    public LockAwareAggregate(A wrappedAggregate, TopicLock lock) {
        this.wrappedAggregate = wrappedAggregate;
        this.lock = () -> lock;
    }

    /**
     * Initializes a new {@link LockAwareAggregate} for given {@code wrappedAggregate} and a supplier of the {@code
     * lock}.
     *
     * @param wrappedAggregate the aggregate instance to which the LockAwareAggregate will delegate
     * @param lock             a supplier of the lock held by the aggregate
     */
    public LockAwareAggregate(A wrappedAggregate, Supplier<TopicLock> lock) {
        this.wrappedAggregate = wrappedAggregate;
        this.lock = lock::get;
    }


    public A getWrappedAggregate() {
        return wrappedAggregate;
    }

    public boolean isLockHeld() {
        return this.lock.acquire().isHeld();
    }

    @Override
    public String type() {
        return wrappedAggregate.type();
    }

    @Override
    public AnnotatedBizIdentifier<?> aggregateRootId() {
        return wrappedAggregate.aggregateRootId();
    }

    @Override
    public AR aggregateRoot() {
        return wrappedAggregate.aggregateRoot();
    }

    @Override
    public String aggregateName() {
        return wrappedAggregate.aggregateName();
    }

    @Override
    public Object identifier() {
        return wrappedAggregate.identifier();
    }

    @Override
    public Long version() {
        return wrappedAggregate.version();
    }

    @Override
    public Object handle(Message<?> message) throws Exception {
        Object result = wrappedAggregate.handle(message);
        // we need to ensure the lock is acquired, as this may not have happened earlier
        lock.acquire();
        return result;
    }


    @Override
    public boolean isDeleted() {
        return wrappedAggregate.isDeleted();
    }

    @Override
    public boolean isNew() {
        return wrappedAggregate.isNew();
    }

    @Override
    public Integer fetchSource() {
        return wrappedAggregate.fetchSource();
    }

    @Override
    public void updateFetchSource(Integer fetchSource) {
        wrappedAggregate.updateFetchSource(fetchSource);
    }

    @Override
    public Class<? extends AR> rootType() {
        return wrappedAggregate.rootType();
    }

    @FunctionalInterface
    private interface LockSupplier extends Supplier<TopicLock> {

        default TopicLock acquire() {
            return this.get();
        }
    }
}
